﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

/*
Todo:
	- formal definition for events. 
	- multiple filters?
*/
namespace SqlServer.Profiler
{
	public partial class ProfileConfigurationForm : Form
	{
		/// <summary>
		/// enumerates the available tabs to display when the form is displayed
		/// </summary>
		public enum Tab {events, columns, filters};
		/// <summary>
		/// Construct and configure the form
		/// </summary>
		/// <param name="events">Array of events to select</param>
		/// <param name="columns">Array of columns to select</param>
		/// <param name="initialTab">tab to switch to when the form displays</param>
		public ProfileConfigurationForm(String name, ProfilerEventsConfiguration[] events, ProfilerColumnConfiguration[] columns, Tab initialTab)
		{
			InitializeComponent();
			ConfigName = name;
			this.columns = columns;
			InitialiseControls(name, this.columns,  events);
			OnChangeColumnSelection(null);
			
			// set to events unless otherwise directed
			this.tabControl1.SelectedIndex = (int)initialTab; 
			if (initialTab == Tab.filters)
				InitialiseFilterControls();
		}
		///////////////////////////////////////////////////////////////////////////////////
		// Getters
		/// <summary>
		/// The name of the configuration being modified,
		/// </summary>
		public String ConfigName {get; set; }

		/// <summary>
		/// An array of the currently selected events
		/// </summary>
		public ProfilerEventsConfiguration[] SelectedEvents
		{
			get
			{
				ProfilerEventsConfiguration[] outArray = new ProfilerEventsConfiguration[listSelectedEvents.Items.Count];
				int idx = 0;
				foreach (object o in listSelectedEvents.Items)
				{
					outArray[idx++] = o as ProfilerEventsConfiguration;
				}
				return outArray;
			}
		}
		/// <summary>
		/// An array of the currently selected report columns
		/// </summary>
		public ProfilerColumnConfiguration[] SelectedColumns
		{
			get
			{
				ProfilerColumnConfiguration[] outArray = new ProfilerColumnConfiguration[listSelectedColumns.Items.Count];
				int idx = 0;
				foreach (object o in listSelectedColumns.Items)
				{
					outArray[idx++] = o as ProfilerColumnConfiguration;
				}
				return outArray;
			}
		}

		private void InitialiseControls(String name, ProfilerColumnConfiguration[] columns, ProfilerEventsConfiguration[] events)
		{
			// do not allow a name to be entered when configuring the default
			if (String.IsNullOrEmpty(name))
			{
				textConfigName.Text = "Default";
				textConfigName.Enabled = false;
			}
			else
			{
				textConfigName.Text = name;
				// Hint to newbies that they can change the Preset config name
				if (name.IndexOf("Preset") == 0)
					lblNameHint.Visible = true;
			}

			lblFilterHelp.Text = FILTER_HELP;
			Dictionary<String, bool> set = new Dictionary<string,bool>();
			foreach (ProfilerEventsConfiguration evt in ProfilerConfiguration.EventConfig)
			{
				if (!set.ContainsKey(evt.CategoryName))
				{
					comboEventCategory.Items.Add(evt.CategoryName);
					set.Add(evt.CategoryName, true);
				}
			}
			comboEventCategory.SelectedIndex = 0;

			if (events != null)
				foreach (ProfilerEventsConfiguration evt in events)
				{
					listSelectedEvents.Items.Add(evt);
				}

			// this adds all the report columns to the list.
			SetMatchString("");

			if (columns != null)
			{
				foreach (ProfilerColumnConfiguration column in columns)
				{
					listSelectedColumns.Items.Add(column);
				}
			}
		}

		private void InitialiseFilterControls()
		{
			listFilterColumns.Items.Clear();
			foreach (object o in listSelectedColumns.Items)
			{
				listFilterColumns.Items.Add(o);
				// tries to be clever in setting the initial state by selecting the first
				// column with a filter condition attached
				if (listFilterColumns.SelectedItem == null)
					if (!String.IsNullOrEmpty((o as ProfilerColumnConfiguration).Filter))
						listFilterColumns.SelectedItem = o;
			}
			// test for no filter conditions and initialise to the first column
			if (listFilterColumns.SelectedItem == null)
				listFilterColumns.SelectedIndex = 0;
			UpdateFilterDisplay(listFilterColumns, textFilter);
		}
		//////////////////////////////////////////////////////////////////////////////////////
		// Events
		//
		private void TabControl1_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (tabControl1.SelectedIndex == (int)Tab.filters)
				InitialiseFilterControls();
		}
		/////////////////////////////////////////////////////////////////////////////////////
		// button events
		//
		private void BtnOk_Click(object sender, EventArgs e)
		{
			if (ValidatesOk())
			{
				this.DialogResult = System.Windows.Forms.DialogResult.OK;
				this.Close();
			}
		}

		private void BtnCancel_Click(object sender, EventArgs e)
		{
			this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
			this.Close();
		}
		
		// Events tab
		private void BtnAddEvent_Click(object sender, EventArgs e) { OnAdd(listEvents, listSelectedEvents); }
		private void BtnDeleteEvent_Click(object sender, EventArgs e) { OnDelete(listSelectedEvents); }
		private void BtnClearEvents_Click(object sender, EventArgs e) { OnClear(listSelectedEvents); }

		// Columns tab
		private void BtnAddColumn_Click(object sender, EventArgs e) { OnAddColumn(); }
		
		private void BtnDeleteColumn_Click(object sender, EventArgs e)
		{
			ProfilerColumnConfiguration column = listSelectedColumns.SelectedItem as ProfilerColumnConfiguration;
			if (column == null) return;
			OnDeleteColumn(listSelectedColumns);
		}
		private void BtnColumnUp_Click(object sender, EventArgs e) { OnMoveColumn(-1); }
		private void BtnColumnDown_Click(object sender, EventArgs e) { OnMoveColumn(1); }
		private void BtnColumnsClear_Click(object sender, EventArgs e) { OnClear(listSelectedColumns); }

		// Filter  tab
		private void BtnClearAllFilters_Click(object sender, EventArgs e) { OnClearAllFilters();}
		private void BtnClearFilter_Click(object sender, EventArgs e) { OnClearFilter(); }
		
		private void BtnFilter_Click(object sender, EventArgs e)
		{
			AddFilter();
		}

		/////////////////////////////////////////////////////////////////////////////////////
		// list and combo events
		//
		// Events tab
		private void ComboEventCategory_SelectedIndexChanged(object sender, EventArgs e) { OnCategorySelectionChanged(); }
		private void ListEvents_MouseDoubleClick(object sender, MouseEventArgs e) { OnAdd(listEvents, listSelectedEvents);}

		// Columns tab
		private void ListColumns_SelectedIndexChanged(object sender, EventArgs e)
		{
			ListBox lb = sender as ListBox;
			ProfilerColumnConfiguration column = lb.SelectedItem as ProfilerColumnConfiguration;
			lblHelp.Text = (column == null) ? "": column.Description;
		}

		private void ListSelectedColumns_SelectedIndexChanged(object sender, EventArgs e)
		{
			ListBox lb = sender as ListBox;
			ProfilerColumnConfiguration column = lb.SelectedItem as ProfilerColumnConfiguration;
			OnChangeColumnSelection(column);
		}

		private void ListColumns_MouseDoubleClick(object sender, MouseEventArgs e) { OnAddColumn(); }

		// Filter  tab
		private void ListFilterColumns_SelectedIndexChanged(object sender, EventArgs e) { OnFilterComboChanged(); }

		private void TextRHS_Enter(object sender, EventArgs e)
		{
			this.AcceptButton = btnAddFilter;
		}

		private void TextRHS_Leave(object sender, EventArgs e)
		{
			this.AcceptButton = null;
		}

		/////////////////////////////////////////////////////////////////////////////////////
		// Actions usually as a result of an event
		//
		// General
		private bool ValidatesOk()
		{
			String message = "";
			if (SelectedEvents.GetLength(0) == 0)
			{
				message = "Please select at least one event to include in the trace\nRecommended: TSQL:SQL:BatchCompleted.";
			}
			if (SelectedColumns.GetLength(0) == 0)
			{
				message = "Please select at least one column to include in the trace report.\nRecommended: TextData, EventClass or LoginName.";
			}

			if (message.Length > 0)
			{
				MessageBox.Show(message, "Error in configuration", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
			// transfer name if everything is ok and not the default one and the user has not cleared it.
			else if (textConfigName.Enabled && !String.IsNullOrEmpty(textConfigName.Text))
				ConfigName = textConfigName.Text;

			// Test whether the filter text or condition has changed 
			if (this.tabControl1.SelectedIndex == (int)Tab.filters)
			{
				ProfileFilter newFilter = MakeFilterFromControls();
				ProfilerColumnConfiguration column = listFilterColumns.SelectedItem as ProfilerColumnConfiguration;
				if (column != null)
				{
					ProfileFilter f = new ProfileFilter();
					ProfileFilter.TryParse(column.Filter, out f);
					if (!(String.IsNullOrEmpty(column.Filter) && String.IsNullOrEmpty(newFilter.Rhs)))  // both are empty test
						if (f.Rhs != newFilter.Rhs || f.Operator != newFilter.Operator)					// new differs from old
							AddFilter();
				}
			}
			// F if an error message was generated
			return (message.Length == 0);
		}

		private object OnAdd(ListBox src, ListBox dst)
		{
			object tgt = src.SelectedItem;
			if (tgt == null) return null;
			if (dst.Items.Contains(tgt)) return null;
			dst.Items.Add(tgt);
			dst.SelectedItem = tgt;
			return tgt;
		}

		private void OnDelete(ListBox src)
		{
			object tgt = src.SelectedItem;
			if (tgt == null) return;
			int holdIndex = src.SelectedIndex;
			src.Items.Remove(tgt);
			src.SelectedIndex = (src.Items.Count == 0) ? -1 :
//								(holdIndex >= src.Items.Count) ? src.Items.Count -1 :  // last on move selection up
								(holdIndex >= src.Items.Count) ?  -1 :  // last one don't move selection up
								holdIndex;
		}

		private void OnClear(ListBox src) { src.Items.Clear(); }

		// Events tab
		private void OnCategorySelectionChanged()
		{
			String seek = this.comboEventCategory.Text.ToUpper();
			listEvents.Items.Clear();
			foreach (ProfilerEventsConfiguration evt in ProfilerConfiguration.EventConfig)
			{
				if (EventMatches(evt, seek))
					listEvents.Items.Add(evt);
			}
		}

		private bool EventMatches(ProfilerEventsConfiguration evt, string seek)
		{
			if (evt.ToString().ToUpper().IndexOf(seek) != -1)
				return true;
			return false;
		}

		// Columns tab
		private void OnAddColumn()
		{
			ProfilerColumnConfiguration column = listColumns.SelectedItem as ProfilerColumnConfiguration;
			if (column == null) return;
			OnAdd(listColumns, listSelectedColumns);
		}

		private void OnDeleteColumn(ListBox listSelectedColumns) { OnDelete(listSelectedColumns); }

		private void OnMoveColumn(int dir)
		{
			// find selected
			ProfilerColumnConfiguration column = listSelectedColumns.SelectedItem as ProfilerColumnConfiguration;
			if (column == null) return;
			// determine insert pos
			int pos = listSelectedColumns.SelectedIndex;
			// can it move?
			if (pos == 0 && dir == -1) return; // up and at top
			if (pos == listSelectedColumns.Items.Count -1 && dir == 1) return; // down and at end
			// remove
			listSelectedColumns.Items.Remove(column);
			// insert
			listSelectedColumns.Items.Insert(pos + dir, column);
			// reset as selected
			listSelectedColumns.SelectedIndex = pos + dir;
		}

		// Filter tab
		private void AddFilter()
		{
			ProfilerColumnConfiguration column = listFilterColumns.SelectedItem as ProfilerColumnConfiguration;
			ProfileFilter f = MakeFilterFromControls();
			column.Filter = f.ToString();
			UpdateFilterDisplay(listFilterColumns, textFilter);
		}

		private ProfileFilter MakeFilterFromControls()
		{
			ProfileFilter f = new ProfileFilter();
			ProfilerColumnConfiguration column = listFilterColumns.SelectedItem as ProfilerColumnConfiguration;
			if (column != null)
			{
				f.ColumnId = column.Id;
				f.Rhs = textRHS.Text;
				ProfileFilter.Op op;
				ProfileFilter.TryParse(comboCondition.SelectedItem.ToString(), out op);
				f.Operator = op;
			}
			return f;
		}

		private void OnClearFilter()
		{
			ProfilerColumnConfiguration column = listFilterColumns.SelectedItem as ProfilerColumnConfiguration;
			if (column == null) return;
			column.Filter = "";
			SetFilterControls(column, false);
			UpdateFilterDisplay(listFilterColumns, textFilter);
		}

		private void OnClearAllFilters()
		{
			//bool first = true;
			foreach (object o in listFilterColumns.Items)
			{
				ProfilerColumnConfiguration column = o as ProfilerColumnConfiguration;
				column.Filter = "";
				//if (first)
				//{
				//    OnChangeColumnSelection(column);
				//    first = false;
				//}
			}
			UpdateFilterDisplay(listFilterColumns, textFilter);

			listFilterColumns.SelectedIndex = (listFilterColumns.Items.Count == 0) ? -1 : 0;
		}

		private void UpdateFilterDisplay(ListBox lb, TextBox tb)
		{
			tb.Text = "";
			bool first = true;
			foreach (object o in lb.Items)
			{
				ProfilerColumnConfiguration column = o as ProfilerColumnConfiguration;
				if (column != null && String.IsNullOrEmpty(column.Filter) == false)
				{
					ProfileFilter f;
					if (ProfileFilter.TryParse(column.Filter, out f))
					{
						if (first)
							first = false;
						else
							tb.Text += " AND ";
						tb.Text += column.Title + " " + f.OperatorName + " " + f.Rhs;
					}
				}
			}
		}

		private void OnFilterComboChanged()
		{
		    ProfilerColumnConfiguration column = listFilterColumns.SelectedItem as ProfilerColumnConfiguration;
		    SetFilterControls(column, false);
		}

		private void OnChangeColumnSelection(ProfilerColumnConfiguration column)
		{
			SetFilterControls(column, true);
		}

		private void SetFilterControls(ProfilerColumnConfiguration column, bool resetComboIndex)
		{
			comboCondition.Items.Clear();
			if (column == null)
			{
				lblSelected.Text = "";
				textRHS.Text = "";
				return;
			}
			
			lblSelected.Text = column.Title;

			ProfileFilter f;
			bool hasFilter = ProfileFilter.TryParse(column.Filter, out f);
			textRHS.Text = (hasFilter) ? f.Rhs : "";

			switch (column.Type)
			{
			    case "S":
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.like, false));
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.notlike, false));
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.eq, false));
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.noteq, false));
			        break;
			    default:
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.gteq, false));
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.lteq, false));
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.eq, false));
			        comboCondition.Items.Add(ProfileFilter.GetOpCode(ProfileFilter.Op.noteq, false));
			        break;
			}
			// check condition later
			comboCondition.SelectedIndex = 0;
			// only set the condition if there is an existing filter.
			if (hasFilter)
			{
				int idx = 0;
				foreach (object o in comboCondition.Items)
				{
					if (o.ToString() == f.OperatorName)
					{
						comboCondition.SelectedIndex = idx;
						break;
					}
					++idx;
				}
			}
		}

		//////////////////////////////////////////////////////////////////////////////////////
		// Column selection cut-down methods
		//
		private void SetMatchString(String newMatch)
		{
			if (newMatch.CompareTo(this.matchString) == 0)
				return; // do nothing if nothing to do.
			this.matchString = MakeSearchText(newMatch);
			PopulateColumns(this.matchString);
		}
		
		// deal with noise chars
		static private String MakeSearchText(String source)
		{
			return source.Replace("_", "").Replace("-", "").Replace(" ", "").ToUpper();
		}

		private bool Matches(String target, String pattern)
		{
			if (pattern.Length == 0) // no search set
				return true;
			// simple contains
			if (MakeSearchText(target).IndexOf(pattern) >= 0)
				return true;
			return false;
		}

		private void PopulateColumns(String strMatch)
		{
			listColumns.Items.Clear();
			if (strMatch.Length > 0)
			{
				listColumns.ForeColor = System.Drawing.Color.Red;
				listColumns.Sorted = true;
			}
			else
			{
				listColumns.ForeColor = System.Drawing.SystemColors.WindowText;
				listColumns.Sorted = false;
			}

			object o = listColumns.SelectedItem;
			foreach (ProfilerColumnConfiguration column in ProfilerConfiguration.ColumnConfig)
			{
				String title = column.Title;
				if (Matches(title, strMatch))
					listColumns.Items.Add(column);
			}
			if (o != null && listColumns.Items.Contains(o))
				listColumns.SelectedItem = o;
			else
				listColumns.SelectedIndex = (listColumns.Items.Count == 0) ? -1 : 0;
		}

		private void ListColumns_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
		{
			char c = e.KeyChar;
			if ((c >= '0' && c <= '9')
				|| (c >= 'A' && c <= 'Z')
				|| (c >= 'a' && c <= 'z')
				|| (c == '_' || c == '_'))
			{
				SetMatchString(this.matchString + c);
				e.Handled = true;
			}
			else if (c == 8)
			{
				int l = this.matchString.Length;
				if (l > 0)
				{
					SetMatchString(this.matchString.Substring(0, l - 1));
					e.Handled = true;
				}
			}
			else if (c == 27)
			{
				SetMatchString("");
				e.Handled = true;
			}
			else if (c == 32)
			{
				// 
				OnAddColumn();
				SetMatchString("");
				e.Handled = true;
			}
		}

		ProfilerColumnConfiguration[] columns;

		String matchString;

		const String FILTER_HELP = @"Enter the search condition and hit enter or press [Set]. For multiple string conditions separate them with , to match any word; && to match all words.";
	}
}
